// HSS.Logging.Management.SqlLogReader.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       SqlLogReader.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.Logging.Management
{
	#region Using Directives
	using System;
	using System.Collections.Generic;
	using System.Data;
	using System.Diagnostics;
	using System.IO;
	using System.Security.Permissions;
	using System.ServiceModel;
	using System.Text;
	using System.Threading;
	using HSS.Data;
	#endregion

	/// <summary>
	/// The SqlLogReader maintains and exposes access to the LogServer
	/// </summary>
#if !DEBUG
	[PrincipalPermissionAttribute(SecurityAction.Demand, Role = "Administrators")]
#endif
	internal sealed class SqlLogReader
	{
		#region Fields
		private string tempFolder = Path.Combine(Path.GetTempPath(), "eventLogViewer");
		private string tempHtml = string.Empty;
		private string html = HSS.Logging.Management.Resources.EventLogAttachment;
		private NetNamedPipeBinding binding;
		private EndpointAddress epa;
		private Dictionary<int, LogEntryAttachment> attachmentCache = new Dictionary<int, LogEntryAttachment>();
		#endregion

		#region Constructor
		/// <summary>
		/// Constructor
		/// </summary>
		public SqlLogReader()
		{
			this.binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
			this.binding.MaxReceivedMessageSize = 0x7fffffffL;
#if DEBUG
			this.epa = new EndpointAddress(Log.HSSLogServerURI + "debug");
#else
			this.epa = new EndpointAddress(Log.HSSLogServerURI);
#endif
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets the logs current configuration
		/// </summary>
		public LogConfiguration LogConfig
		{
			get
			{
				return this._logConfig;
			}
		} LogConfiguration _logConfig;
		#endregion

		#region Methods

		private void ClearLogThreadProc(object asyncResult)
		{
			// We know that it's really an AsyncResultNoResult object
			AsyncResultNoResult ar = (AsyncResultNoResult)asyncResult;
			try
			{
				// Perform the operation; if sucessful set the result
				this.ClearLog((int)ar.AsyncState);
				ar.SetAsCompleted(null, false);
			}
			catch (Exception e)
			{
				// If operation fails, set the exception
				e.Source = "SqlLogReader.ClearLog(DaysToRetain)";
				ar.SetAsCompleted(e, false);
			}
		}
		private void GetAttachmentThreadProc(object asyncResult)
		{
			AsyncResult<LogEntryAttachment> ar = (AsyncResult<LogEntryAttachment>)asyncResult;
			try
			{
				// Perform the operation; if sucessful set the result
				int AttachmentId = (int)ar.AsyncState;
				LogEntryAttachment lea = GetAttachment(AttachmentId);
				ar.SetAsCompleted(lea, false);
			}
			catch (Exception e)
			{
				// If operation fails, set the exception
				e.Source = "SqlLogReader.GetAttachment(AttachmentId)";
				ar.SetAsCompleted(e, false);
			}
		}
		private void GetEntriesThreadProc(object asyncResult)
		{
			AsyncResult<List<LogRecord>> ar = (AsyncResult<List<LogRecord>>)asyncResult;
			try
			{
				// Perform the operation; if successful set the result

				int pages = 0;
				var param = (object[])ar.AsyncState;

				var Page = (int)param[0];
				var RecPerPage = (int)param[1];
				var EventTypeMask = (int)param[2];
				var ApplicationName = (string)param[3];
				var ReferenceNumber = (string)param[4];
				var Category = (string)param[5];
				var UserName = (string)param[6];
				var BeginDate = (DateTime)param[7];
				var EndDate = (DateTime)param[8];

				var dt = GetEntries(ref pages, Page, RecPerPage, EventTypeMask, ApplicationName, ReferenceNumber, Category, UserName, BeginDate, EndDate);
				ar.AsyncState = pages;
				ar.SetAsCompleted(dt, false);
			}
			catch (Exception e)
			{
				// If operation fails, set the exception
				e.Source = "SqlLogReader.GetEntries(Pages, Page, RecPerPage, ReferenceNumber, Category, EventTypeMask, UserName, BeginDate, EndDate)";
				ar.SetAsCompleted(e, false);
			}
		}
		private void GetCategoriesThreadProc(object asyncResult)
		{
			AsyncResult<List<LogCategory>> ar = (AsyncResult<List<LogCategory>>)asyncResult;
			try
			{
				// Perform the operation; if sucessful set the result
				List<LogCategory> categories = GetCategories();
				ar.SetAsCompleted(categories, false);
			}
			catch (Exception e)
			{
				// If operation fails, set the exception
				e.Source = "SqlLogReader.GetCategories()";
				ar.SetAsCompleted(e, false);
			}
		}


		/// <summary>
		/// Initialize the SqlLogReader.
		/// </summary>
		/// <returns>true if LogServer is online and available</returns>
		internal bool InitializeLogReader()
		{
			try
			{
				HSS.IO.FileUtil.EnsureDirectoryExist(tempFolder);

				foreach (string f in Directory.GetFiles(tempFolder))
				{
					try { File.Delete(f); }
					catch { }
				}

				tempHtml = Path.Combine(tempFolder, "html");
				HSS.IO.FileUtil.EnsureDirectoryExist(tempHtml);
				tempHtml = Path.Combine(tempHtml, "tempHtml.html");
				if (!File.Exists(tempHtml))
				{
					using (FileStream fs = File.OpenWrite(tempHtml))
					{
						byte[] buffer = UTF8Encoding.UTF8.GetBytes(html);
						fs.Write(buffer, 0, buffer.Length);
					}
				}

				using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
				{
					channel.Open();
					if (channel.Ping())
					{
						this._logConfig = channel.GetConfig();
						return true;
					}
				}

			}
			catch { }

			return false;
		}


		/// <summary>
		/// Clear the entries from the log asyncronsously
		/// </summary>
		/// <param name="callback"></param>
		/// <param name="daysToRetain"></param>
		/// <returns>AsyncResultNoResult</returns>
		public IAsyncResult BeginClearLog(AsyncCallback callback, int daysToRetain)
		{
			// Create IAsyncResult object identifying the asynchronous operation
			AsyncResultNoResult ar = new AsyncResultNoResult(callback, daysToRetain);

			ThreadPool.QueueUserWorkItem(ClearLogThreadProc, ar);

			// Return the IAsyncResult to the caller
			return ar;
		}
		/// <summary>
		/// End the call to ClearLog, waiting for completion and result
		/// </summary>
		/// <param name="asyncResult">The IAsyncResult provided from BeginClearLog</param>
		public static void EndClearLog(IAsyncResult asyncResult)
		{
			// We know that the IAsyncResult is really an AsyncResultNoResult object
			AsyncResultNoResult ar = (AsyncResultNoResult)asyncResult;

			// Wait for operation to complete
			ar.EndInvoke();
		}
		/// <summary>
		/// Clear the entries from the log
		/// </summary>
		/// <param name="daysToRetain">The number of days to retain</param>
		public void ClearLog(int daysToRetain)
		{
			if (null != attachmentCache)
				attachmentCache.Clear();
			using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
				channel.TruncateLog(daysToRetain);
		}

		/// <summary>
		/// Retrieve the list of available categories asyncronsously
		/// </summary>
		/// <returns>AsyncResult DataRowReader</returns>
		public IAsyncResult BeginGetCategories(AsyncCallback callback)
		{
			// Create IAsyncResult object identifying the asynchronous operation
			AsyncResult<List<LogCategory>> ar = new AsyncResult<List<LogCategory>>(callback, null);

			ThreadPool.QueueUserWorkItem(GetCategoriesThreadProc, ar);

			// Return the IAsyncResult to the caller
			return ar;
		}
		/// <summary>
		/// End the call to GetCategories, waiting for completion and result
		/// </summary>
		/// <param name="asyncResult">The IAsyncResult provided from BeginGetCategories</param>
		/// <returns>A List of <see cref="LogCategory"/> objects.</returns>
		public static List<LogCategory> EndGetCategories(IAsyncResult asyncResult)
		{
			// We know that the IAsyncResult is really an AsyncResult<LogEntryAttachment> object
			AsyncResult<List<LogCategory>> ar = (AsyncResult<List<LogCategory>>)asyncResult;

			// Wait for operation to complete, then return result or throw exception
			return ar.EndInvoke();
		}
		/// <summary>
		/// Retrieve the list of Categories from the Database.
		/// </summary>
		/// <returns>A List of <see cref="LogCategory"/> objects.</returns>
		public List<LogCategory> GetCategories()
		{
			using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
				return channel.GetCategories();
		}

		/// <summary>
		/// Gets a filtered list of entries from the HSS Database EventLog table asynchronously.
		/// </summary>
		/// <param name="callback">The <see cref="AsyncCallback"/> delegate to call when the operation completes.</param>
		/// <param name="page">The desired page.</param>
		/// <param name="recPerPage">The number of records per page.</param>
		/// <param name="eventTypeMask">The Mask of EventTypes to filter for.</param>
		/// <param name="applicationName">The Application to filter for.</param>
		/// <param name="referenceNumber">The Reference Number to filter for.</param>
		/// <param name="category">The category to filter for.</param>
		/// <param name="userName">The UserName associated with the entry.</param>
		/// <param name="beginDate">The Created Date of the entry must be greater than or equal to this date.</param>
		/// <param name="endDate">The Created Date of the entry must be less than or equal to this date.</param>
		/// <returns>The IAsyncResult object.</returns>
		public IAsyncResult BeginGetEntries(AsyncCallback callback, int page, int recPerPage, int eventTypeMask, string applicationName, string referenceNumber, string category, string userName, DateTime beginDate, DateTime endDate)
		{
			// Create IAsyncResult object identifying the asynchronous operation
			AsyncResult<List<LogRecord>> ar = new AsyncResult<List<LogRecord>>(callback, new object[] { 
				page, recPerPage, eventTypeMask, applicationName, referenceNumber, category, userName, beginDate, endDate });

			ThreadPool.QueueUserWorkItem(GetEntriesThreadProc, ar);

			// Return the IAsyncResult to the caller
			return ar;
		}
		/// <summary>
		/// End the call to GetEntries, waiting for completion and result
		/// </summary>
		/// <param name="asyncResult">The IAsyncResult provided from BeginGetEntries</param>
		/// <param name="pages">The number of pages found</param>
		/// <returns>The List of LogRecords containin the entries retrieved</returns>
		public static List<LogRecord> EndGetEntries(IAsyncResult asyncResult, ref int pages)
		{
			// We know that the IAsyncResult is really an AsyncResult<LogEntryAttachment> object
			AsyncResult<List<LogRecord>> ar = (AsyncResult<List<LogRecord>>)asyncResult;

			// Wait for operation to complete, then return result or throw exception
			List<LogRecord> dt = ar.EndInvoke();
			pages = (int)ar.AsyncState;
			return dt;
		}
		/// <summary>
		/// Gets a filtered list of entries from the HSS Database EventLog table.
		/// </summary>
		/// <param name="pages">Reference to the number of pages.</param>
		/// <param name="page">The desired page.</param>
		/// <param name="recPerPage">The number of records per page.</param>
		/// <param name="eventTypeMask">The Mask of EventTypes to filter for.</param>
		/// <param name="applicationName">The Application to filter for.</param>
		/// <param name="referenceNumber">The Reference Number to filter for.</param>
		/// <param name="category">The category to filter for.</param>
		/// <param name="userName">The UserName associated with the entry.</param>
		/// <param name="beginDate">The Created Date of the entry must be greater than or equal to this date.</param>
		/// <param name="endDate">The Created Date of the entry must be less than or equal to this date.</param>
		/// <returns>A list of LogRecords containing the EventLog data.</returns>
		public List<LogRecord> GetEntries(ref int pages, int page, int recPerPage, int eventTypeMask, string applicationName, string referenceNumber, string category, string userName, DateTime beginDate, DateTime endDate)
		{
			using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
				return channel.GetEntries(ref pages, page, recPerPage, eventTypeMask, applicationName, referenceNumber, category, userName, beginDate, endDate);
		}

		/// <summary>
		/// Begin to Get an Attachment Asynchronous 
		/// </summary>
		/// <param name="attachmentId">The Id of the attachment to retrieve</param>
		/// <param name="callback">An AsyncCallback to call upon completion</param>
		/// <returns>AsyncResult LogEntryAttachment</returns>
		public IAsyncResult BeginGetAttachment(AsyncCallback callback, int attachmentId)
		{
			// Create IAsyncResult object identifying the asynchronous operation
			AsyncResult<LogEntryAttachment> ar = new AsyncResult<LogEntryAttachment>(callback, attachmentId);

			// If already exists, just set as completed
			if (attachmentCache.ContainsKey(attachmentId))
				ar.SetAsCompleted(attachmentCache[attachmentId], true);
			else // Use a thread pool thread to perform the operation
			{
				ThreadPool.QueueUserWorkItem(GetAttachmentThreadProc, ar);
			}

			// Return the IAsyncResult to the caller
			return ar;
		}
		/// <summary>
		/// End the call to GetAttachment, waiting for completion and result
		/// </summary>
		/// <param name="asyncResult">The IAsyncResult provided from BeginGetAttachment</param>
		/// <returns>The LogEntryAttachment requested</returns>
		public static LogEntryAttachment EndGetAttachment(IAsyncResult asyncResult)
		{
			// We know that the IAsyncResult is really an AsyncResult<LogEntryAttachment> object
			AsyncResult<LogEntryAttachment> ar = (AsyncResult<LogEntryAttachment>)asyncResult;

			// Wait for operation to complete, then return result or throw exception
			return ar.EndInvoke();
		}
		/// <summary>
		/// Retrieve an Attachment from the LogServer
		/// </summary>
		/// <param name="attachmentId">The Id of the attachment to retrieve</param>
		/// <returns>The LogEntryAttachment requested</returns>
		public LogEntryAttachment GetAttachment(int attachmentId)
		{
			LogEntryAttachment att = new LogEntryAttachment();

			if (attachmentId == 0)
				return att;

			if (attachmentCache.ContainsKey(attachmentId))
			{
				#region Get from the Cache
				att = this.attachmentCache[attachmentId];
				#endregion
			}
			else
			{
				#region Get from the LogServer
				using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
					att = channel.GetAttachment(attachmentId);
				#endregion

				#region Persist to disk and cache
				// If no DisplayName, then skip caching.
				if (string.IsNullOrEmpty(att.DisplayName))
					return att;
				//
				// Since this method could be called asyncronously, the user could select it
				// a second time even though it's already successfully downloaded once.
				// So if it already exists, just skip.
				//
				if (!this.attachmentCache.ContainsKey(attachmentId))
				{
					// Get file name
					string tempFile = Path.Combine(this.tempFolder, att.DisplayName);
					int fileIndex = 0;
					string tmp = tempFile;
					while (File.Exists(tmp))
					{
						tmp = Path.Combine(this.tempFolder, att.LocalFileName + "[" + fileIndex.ToString() + "]" + att.Extension);
						fileIndex++;
					}
					tempFile = tmp;

					// re-use the localfilename for the real file saved to disk
					att.LocalFileName = tempFile;
					try
					{
						using (FileStream fs = File.OpenWrite(tempFile))
							fs.Write(att.Content, 0, att.Content.Length);
						this.attachmentCache.Add(attachmentId, att);
					}
					catch
					{
						try { File.Delete(tempFile); }
						catch { }
					}
				}
				#endregion
			}

			return att;

		}

		/// <summary>
		/// Retrieve an Attachment from the local cache
		/// </summary>
		/// <param name="attachmentId">The Id of the attachment to retrieve</param>
		/// <returns>The LogEntryAttachment requested from cache or null if not in the cache</returns>
		public LogEntryAttachment GetAttachmentFromCache(int attachmentId)
		{
			if (attachmentCache.ContainsKey(attachmentId))
				return attachmentCache[attachmentId];
			else
				return new LogEntryAttachment();
		}

		#endregion
	}
}